/*
 * Copyright (C) 2011-2017 ARM Limited. All rights reserved.
 * 
 * This program is free software and is provided to you under the terms of the GNU General Public License version 2
 * as published by the Free Software Foundation, and any use by you of this program is subject to the terms of such GNU licence.
 * 
 * A copy of the licence is included with the program, and can also be obtained from Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

#include "mali_pm.h"
#include "mali_kernel_common.h"
#include "mali_osk.h"
#include "mali_osk_mali.h"
#include "mali_scheduler.h"
#include "mali_group.h"
#include "mali_pm_domain.h"
#include "mali_pmu.h"

#include "mali_executor.h"
#include "mali_control_timer.h"

#if defined(DEBUG)
u32 num_pm_runtime_resume = 0;
u32 num_pm_updates = 0;
u32 num_pm_updates_up = 0;
u32 num_pm_updates_down = 0;
#endif

#define MALI_PM_DOMAIN_DUMMY_MASK (1 << MALI_DOMAIN_INDEX_DUMMY)

/* lock protecting power state (including pm_domains) */
static _mali_osk_spinlock_irq_t *pm_lock_state = NULL;

/* the wanted domain mask (protected by pm_lock_state) */
static u32 pd_mask_wanted = 0;

/* used to deferring the actual power changes */
static _mali_osk_wq_work_t *pm_work = NULL;

/* lock protecting power change execution */
static _mali_osk_mutex_t *pm_lock_exec = NULL;

/* PMU domains which are actually powered on (protected by pm_lock_exec) */
static u32 pmu_mask_current = 0;

/*
 * domains which marked as powered on (protected by pm_lock_exec)
 * This can be different from pmu_mask_current right after GPU power on
 * if the PMU domains default to powered up.
 */
static u32 pd_mask_current = 0;

static u16 domain_config[MALI_MAX_NUMBER_OF_DOMAINS] = {
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
	1 << MALI_DOMAIN_INDEX_DUMMY
};

/* The relative core power cost */
#define MALI_GP_COST 3
#define MALI_PP_COST 6
#define MALI_L2_COST 1

/*
 *We have MALI_MAX_NUMBER_OF_PP_PHYSICAL_CORES + 1 rows in this matrix
 *because we mush store the mask of different pp cores: 0, 1, 2, 3, 4, 5, 6, 7, 8.
 */
static int mali_pm_domain_power_cost_result[MALI_MAX_NUMBER_OF_PHYSICAL_PP_GROUPS + 1][MALI_MAX_NUMBER_OF_DOMAINS];
/*
 * Keep track of runtime PM state, so that we know
 * how to resume during OS resume.
 */
#ifdef CONFIG_PM_RUNTIME
static mali_bool mali_pm_runtime_active = MALI_FALSE;
#else
/* when kernel don't enable PM_RUNTIME, set the flag always true,
 * for GPU will not power off by runtime */
static mali_bool mali_pm_runtime_active = MALI_TRUE;
#endif

static void mali_pm_state_lock(void);
static void mali_pm_state_unlock(void);
static _mali_osk_errcode_t mali_pm_create_pm_domains(void);
static void mali_pm_set_pmu_domain_config(void);
static u32 mali_pm_get_registered_cores_mask(void);
static void mali_pm_update_sync_internal(void);
static mali_bool mali_pm_common_suspend(void);
static void mali_pm_update_work(void *data);
#if defined(DEBUG)
const char *mali_pm_mask_to_string(u32 mask);
const char *mali_pm_group_stats_to_string(void);
#endif

_mali_osk_errcode_t mali_pm_initialize(void)
{
	_mali_osk_errcode_t err;
	struct mali_pmu_core *pmu;

	pm_lock_state = _mali_osk_spinlock_irq_init(_MALI_OSK_LOCKFLAG_ORDERED,
			_MALI_OSK_LOCK_ORDER_PM_STATE);
	if (NULL == pm_lock_state) {
		mali_pm_terminate();
		return _MALI_OSK_ERR_FAULT;
	}

	pm_lock_exec = _mali_osk_mutex_init(_MALI_OSK_LOCKFLAG_ORDERED,
					    _MALI_OSK_LOCK_ORDER_PM_STATE);
	if (NULL == pm_lock_exec) {
		mali_pm_terminate();
		return _MALI_OSK_ERR_FAULT;
	}

	pm_work = _mali_osk_wq_create_work(mali_pm_update_work, NULL);
	if (NULL == pm_work) {
		mali_pm_terminate();
		return _MALI_OSK_ERR_FAULT;
	}

	pmu = mali_pmu_get_global_pmu_core();
	if (NULL != pmu) {
		/*
		 * We have a Mali PMU, set the correct domain
		 * configuration (default or custom)
		 */

		u32 registered_cores_mask;

		mali_pm_set_pmu_domain_config();

		registered_cores_mask = mali_pm_get_registered_cores_mask();
		mali_pmu_set_registered_cores_mask(pmu, registered_cores_mask);

		MALI_DEBUG_ASSERT(0 == pd_mask_wanted);
	}

	/* Create all power domains needed (at least one dummy domain) */
	err = mali_pm_create_pm_domains();
	if (_MALI_OSK_ERR_OK != err) {
		mali_pm_terminate();
		return err;
	}

	return _MALI_OSK_ERR_OK;
}

void mali_pm_terminate(void)
{
	if (NULL != pm_work) {
		_mali_osk_wq_delete_work(pm_work);
		pm_work = NULL;
	}

	mali_pm_domain_terminate();

	if (NULL != pm_lock_exec) {
		_mali_osk_mutex_term(pm_lock_exec);
		pm_lock_exec = NULL;
	}

	if (NULL != pm_lock_state) {
		_mali_osk_spinlock_irq_term(pm_lock_state);
		pm_lock_state = NULL;
	}
}

struct mali_pm_domain *mali_pm_register_l2_cache(u32 domain_index,
		struct mali_l2_cache_core *l2_cache)
{
	struct mali_pm_domain *domain;

	domain = mali_pm_domain_get_from_mask(domain_config[domain_index]);
	if (NULL == domain) {
		MALI_DEBUG_ASSERT(0 == domain_config[domain_index]);
		domain = mali_pm_domain_get_from_index(
				 MALI_DOMAIN_INDEX_DUMMY);
		domain_config[domain_index] = MALI_PM_DOMAIN_DUMMY_MASK;
	} else {
		MALI_DEBUG_ASSERT(0 != domain_config[domain_index]);
	}

	MALI_DEBUG_ASSERT(NULL != domain);

	mali_pm_domain_add_l2_cache(domain, l2_cache);

	return domain; /* return the actual domain this was registered in */
}

struct mali_pm_domain *mali_pm_register_group(u32 domain_index,
		struct mali_group *group)
{
	struct mali_pm_domain *domain;

	domain = mali_pm_domain_get_from_mask(domain_config[domain_index]);
	if (NULL == domain) {
		MALI_DEBUG_ASSERT(0 == domain_config[domain_index]);
		domain = mali_pm_domain_get_from_index(
				 MALI_DOMAIN_INDEX_DUMMY);
		domain_config[domain_index] = MALI_PM_DOMAIN_DUMMY_MASK;
	} else {
		MALI_DEBUG_ASSERT(0 != domain_config[domain_index]);
	}

	MALI_DEBUG_ASSERT(NULL != domain);

	mali_pm_domain_add_group(domain, group);

	return domain; /* return the actual domain this was registered in */
}

mali_bool mali_pm_get_domain_refs(struct mali_pm_domain **domains,
				  struct mali_group **groups,
				  u32 num_domains)
{
	mali_bool ret = MALI_TRUE; /* Assume all is powered on instantly */
	u32 i;

	mali_pm_state_lock();

	for (i = 0; i < num_domains; i++) {
		MALI_DEBUG_ASSERT_POINTER(domains[i]);
		pd_mask_wanted |= mali_pm_domain_ref_get(domains[i]);
		if (MALI_FALSE == mali_pm_domain_power_is_on(domains[i])) {
			/*
			 * Tell caller that the corresponding group
			 * was not already powered on.
			 */
			ret = MALI_FALSE;
		} else {
			/*
			 * There is a time gap between we power on the domain and
			 * set the power state of the corresponding groups to be on.
			 */
			if (NULL != groups[i] &&
			    MALI_FALSE == mali_group_power_is_on(groups[i])) {
				ret = MALI_FALSE;
			}
		}
	}

	MALI_DEBUG_PRINT(3, ("PM: wanted domain mask = 0x%08X (get refs)\n", pd_mask_wanted));

	mali_pm_state_unlock();

	return ret;
}

mali_bool mali_pm_put_domain_refs(struct mali_pm_domain **domains,
				  u32 num_domains)
{
	u32 mask = 0;
	mali_bool ret;
	u32 i;

	mali_pm_state_lock();

	for (i = 0; i < num_domains; i++) {
		MALI_DEBUG_ASSERT_POINTER(domains[i]);
		mask |= mali_pm_domain_ref_put(domains[i]);
	}

	if (0 == mask) {
		/* return false, all domains should still stay on */
		ret = MALI_FALSE;
	} else {
		/* Assert that we are dealing with a change */
		MALI_DEBUG_ASSERT((pd_mask_wanted & mask) == mask);

		/* Update our desired domain mask */
		pd_mask_wanted &= ~mask;

		/* return true; one or more domains can now be powered down */
		ret = MALI_TRUE;
	}

	MALI_DEBUG_PRINT(3, ("PM: wanted domain mask = 0x%08X (put refs)\n", pd_mask_wanted));

	mali_pm_state_unlock();

	return ret;
}

void mali_pm_init_begin(void)
{
	struct mali_pmu_core *pmu = mali_pmu_get_global_pmu_core();

	_mali_osk_pm_dev_ref_get_sync();

	/* Ensure all PMU domains are on */
	if (NULL != pmu) {
		mali_pmu_power_up_all(pmu);
	}
}

void mali_pm_init_end(void)
{
	struct mali_pmu_core *pmu = mali_pmu_get_global_pmu_core();

	/* Ensure all PMU domains are off */
	if (NULL != pmu) {
		mali_pmu_power_down_all(pmu);
	}

	_mali_osk_pm_dev_ref_put();
}

void mali_pm_update_sync(void)
{
	mali_pm_exec_lock();

	if (MALI_TRUE == mali_pm_runtime_active) {
		/*
		 * Only update if GPU is powered on.
		 * Deactivation of the last group will result in both a
		 * deferred runtime PM suspend operation and
		 * deferred execution of this function.
		 * mali_pm_runtime_active will be false if runtime PM
		 * executed first and thus the GPU is now fully powered off.
		 */
		mali_pm_update_sync_internal();
	}

	mali_pm_exec_unlock();
}

void mali_pm_update_async(void)
{
	_mali_osk_wq_schedule_work(pm_work);
}

void mali_pm_os_suspend(mali_bool os_suspend)
{
	int ret;

	MALI_DEBUG_PRINT(3, ("Mali PM: OS suspend\n"));

	/* Suspend execution of all jobs, and go to inactive state */
	mali_executor_suspend();

	if (os_suspend) {
		mali_control_timer_suspend(MALI_TRUE);
	}

	mali_pm_exec_lock();

	ret = mali_pm_common_suspend();

	MALI_DEBUG_ASSERT(MALI_TRUE == ret);
	MALI_IGNORE(ret);

	mali_pm_exec_unlock();
}

void mali_pm_os_resume(void)
{
	struct mali_pmu_core *pmu = mali_pmu_get_global_pmu_core();

	MALI_DEBUG_PRINT(3, ("Mali PM: OS resume\n"));

	mali_pm_exec_lock();

#if defined(DEBUG)
	mali_pm_state_lock();

	/* Assert that things are as we left them in os_suspend(). */
	MALI_DEBUG_ASSERT(0 == pd_mask_wanted);
	MALI_DEBUG_ASSERT(0 == pd_mask_current);
	MALI_DEBUG_ASSERT(0 == pmu_mask_current);

	MALI_DEBUG_ASSERT(MALI_TRUE == mali_pm_domain_all_unused());

	mali_pm_state_unlock();
#endif

	if (MALI_TRUE == mali_pm_runtime_active) {
		/* Runtime PM was active, so reset PMU */
		if (NULL != pmu) {
			mali_pmu_reset(pmu);
			pmu_mask_current = mali_pmu_get_mask(pmu);

			MALI_DEBUG_PRINT(3, ("Mali PM: OS resume 0x%x \n", pmu_mask_current));
		}

		mali_pm_update_sync_internal();
	}

	mali_pm_exec_unlock();

	/* Start executing jobs again */
	mali_executor_resume();
}

mali_bool mali_pm_runtime_suspend(void)
{
	mali_bool ret;

	MALI_DEBUG_PRINT(3, ("Mali PM: Runtime suspend\n"));

	mali_pm_exec_lock();

	/*
	 * Put SW state directly into "off" state, and do not bother to power
	 * down each power domain, because entire GPU will be powered off
	 * when we return.
	 * For runtime PM suspend, in contrast to OS suspend, there is a race
	 * between this function and the mali_pm_update_sync_internal(), which
	 * is fine...
	 */
	ret = mali_pm_common_suspend();
	if (MALI_TRUE == ret) {
		mali_pm_runtime_active = MALI_FALSE;
	} else {
		/*
		 * Process the "power up" instead,
		 * which could have been "lost"
		 */
		mali_pm_update_sync_internal();
	}

	mali_pm_exec_unlock();

	return ret;
}

void mali_pm_runtime_resume(void)
{
	struct mali_pmu_core *pmu = mali_pmu_get_global_pmu_core();

	mali_pm_exec_lock();

	mali_pm_runtime_active = MALI_TRUE;

#if defined(DEBUG)
	++num_pm_runtime_resume;

	mali_pm_state_lock();

	/*
	 * Assert that things are as we left them in runtime_suspend(),
	 * except for pd_mask_wanted which normally will be the reason we
	 * got here (job queued => domains wanted)
	 */
	MALI_DEBUG_ASSERT(0 == pd_mask_current);
	MALI_DEBUG_ASSERT(0 == pmu_mask_current);

	mali_pm_state_unlock();
#endif

	if (NULL != pmu) {
		mali_pmu_reset(pmu);
		pmu_mask_current = mali_pmu_get_mask(pmu);
		MALI_DEBUG_PRINT(3, ("Mali PM: Runtime resume 0x%x \n", pmu_mask_current));
	}

	/*
	 * Normally we are resumed because a job has just been queued.
	 * pd_mask_wanted should thus be != 0.
	 * It is however possible for others to take a Mali Runtime PM ref
	 * without having a job queued.
	 * We should however always call mali_pm_update_sync_internal(),
	 * because this will take care of any potential mismatch between
	 * pmu_mask_current and pd_mask_current.
	 */
	mali_pm_update_sync_internal();

	mali_pm_exec_unlock();
}

#if MALI_STATE_TRACKING
u32 mali_pm_dump_state_domain(struct mali_pm_domain *domain,
			      char *buf, u32 size)
{
	int n = 0;

	n += _mali_osk_snprintf(buf + n, size - n,
				"\tPower domain: id %u\n",
				mali_pm_domain_get_id(domain));

	n += _mali_osk_snprintf(buf + n, size - n,
				"\t\tMask: 0x%04x\n",
				mali_pm_domain_get_mask(domain));

	n += _mali_osk_snprintf(buf + n, size - n,
				"\t\tUse count: %u\n",
				mali_pm_domain_get_use_count(domain));

	n += _mali_osk_snprintf(buf + n, size - n,
				"\t\tCurrent power state: %s\n",
				(mali_pm_domain_get_mask(domain) & pd_mask_current) ?
				"On" : "Off");

	n += _mali_osk_snprintf(buf + n, size - n,
				"\t\tWanted power state: %s\n",
				(mali_pm_domain_get_mask(domain) & pd_mask_wanted) ?
				"On" : "Off");

	return n;
}
#endif

static void mali_pm_state_lock(void)
{
	_mali_osk_spinlock_irq_lock(pm_lock_state);
}

static void mali_pm_state_unlock(void)
{
	_mali_osk_spinlock_irq_unlock(pm_lock_state);
}

void mali_pm_exec_lock(void)
{
	_mali_osk_mutex_wait(pm_lock_exec);
}

void mali_pm_exec_unlock(void)
{
	_mali_osk_mutex_signal(pm_lock_exec);
}

static void mali_pm_domain_power_up(u32 power_up_mask,
				    struct mali_group *groups_up[MALI_MAX_NUMBER_OF_GROUPS],
				    u32 *num_groups_up,
				    struct mali_l2_cache_core *l2_up[MALI_MAX_NUMBER_OF_L2_CACHE_CORES],
				    u32 *num_l2_up)
{
	u32 domain_bit;
	u32 notify_mask = power_up_mask;

	MALI_DEBUG_ASSERT(0 != power_up_mask);
	MALI_DEBUG_ASSERT_POINTER(groups_up);
	MALI_DEBUG_ASSERT_POINTER(num_groups_up);
	MALI_DEBUG_ASSERT(0 == *num_groups_up);
	MALI_DEBUG_ASSERT_POINTER(l2_up);
	MALI_DEBUG_ASSERT_POINTER(num_l2_up);
	MALI_DEBUG_ASSERT(0 == *num_l2_up);

	MALI_DEBUG_ASSERT_LOCK_HELD(pm_lock_exec);
	MALI_DEBUG_ASSERT_LOCK_HELD(pm_lock_state);

	MALI_DEBUG_PRINT(5,
			 ("PM update:      Powering up domains: . [%s]\n",
			  mali_pm_mask_to_string(power_up_mask)));

	pd_mask_current |= power_up_mask;

	domain_bit = _mali_osk_fls(notify_mask);
	while (0 != domain_bit) {
		u32 domain_id = domain_bit - 1;
		struct mali_pm_domain *domain =
			mali_pm_domain_get_from_index(
				domain_id);
		struct mali_l2_cache_core *l2_cache;
		struct mali_l2_cache_core *l2_cache_tmp;
		struct mali_group *group;
		struct mali_group *group_tmp;

		/* Mark domain as powered up */
		mali_pm_domain_set_power_on(domain, MALI_TRUE);

		/*
		 * Make a note of the L2 and/or group(s) to notify
		 * (need to release the PM state lock before doing so)
		 */

		_MALI_OSK_LIST_FOREACHENTRY(l2_cache,
					    l2_cache_tmp,
					    mali_pm_domain_get_l2_cache_list(
						    domain),
					    struct mali_l2_cache_core,
					    pm_domain_list) {
			MALI_DEBUG_ASSERT(*num_l2_up <
					  MALI_MAX_NUMBER_OF_L2_CACHE_CORES);
			l2_up[*num_l2_up] = l2_cache;
			(*num_l2_up)++;
		}

		_MALI_OSK_LIST_FOREACHENTRY(group,
					    group_tmp,
					    mali_pm_domain_get_group_list(domain),
					    struct mali_group,
					    pm_domain_list) {
			MALI_DEBUG_ASSERT(*num_groups_up <
					  MALI_MAX_NUMBER_OF_GROUPS);
			groups_up[*num_groups_up] = group;

			(*num_groups_up)++;
		}

		/* Remove current bit and find next */
		notify_mask &= ~(1 << (domain_id));
		domain_bit = _mali_osk_fls(notify_mask);
	}
}
static void mali_pm_domain_power_down(u32 power_down_mask,
				      struct mali_group *groups_down[MALI_MAX_NUMBER_OF_GROUPS],
				      u32 *num_groups_down,
				      struct mali_l2_cache_core *l2_down[MALI_MAX_NUMBER_OF_L2_CACHE_CORES],
				      u32 *num_l2_down)
{
	u32 domain_bit;
	u32 notify_mask = power_down_mask;

	MALI_DEBUG_ASSERT(0 != power_down_mask);
	MALI_DEBUG_ASSERT_POINTER(groups_down);
	MALI_DEBUG_ASSERT_POINTER(num_groups_down);
	MALI_DEBUG_ASSERT(0 == *num_groups_down);
	MALI_DEBUG_ASSERT_POINTER(l2_down);
	MALI_DEBUG_ASSERT_POINTER(num_l2_down);
	MALI_DEBUG_ASSERT(0 == *num_l2_down);

	MALI_DEBUG_ASSERT_LOCK_HELD(pm_lock_exec);
	MALI_DEBUG_ASSERT_LOCK_HELD(pm_lock_state);

	MALI_DEBUG_PRINT(5,
			 ("PM update:      Powering down domains: [%s]\n",
			  mali_pm_mask_to_string(power_down_mask)));

	pd_mask_current &= ~power_down_mask;

	domain_bit = _mali_osk_fls(notify_mask);
	while (0 != domain_bit) {
		u32 domain_id = domain_bit - 1;
		struct mali_pm_domain *domain =
			mali_pm_domain_get_from_index(domain_id);
		struct mali_l2_cache_core *l2_cache;
		struct mali_l2_cache_core *l2_cache_tmp;
		struct mali_group *group;
		struct mali_group *group_tmp;

		/* Mark domain as powered down */
		mali_pm_domain_set_power_on(domain, MALI_FALSE);

		/*
		 * Make a note of the L2s and/or groups to notify
		 * (need to release the PM state lock before doing so)
		 */

		_MALI_OSK_LIST_FOREACHENTRY(l2_cache,
					    l2_cache_tmp,
					    mali_pm_domain_get_l2_cache_list(domain),
					    struct mali_l2_cache_core,
					    pm_domain_list) {
			MALI_DEBUG_ASSERT(*num_l2_down <
					  MALI_MAX_NUMBER_OF_L2_CACHE_CORES);
			l2_down[*num_l2_down] = l2_cache;
			(*num_l2_down)++;
		}

		_MALI_OSK_LIST_FOREACHENTRY(group,
					    group_tmp,
					    mali_pm_domain_get_group_list(domain),
					    struct mali_group,
					    pm_domain_list) {
			MALI_DEBUG_ASSERT(*num_groups_down <
					  MALI_MAX_NUMBER_OF_GROUPS);
			groups_down[*num_groups_down] = group;
			(*num_groups_down)++;
		}

		/* Remove current bit and find next */
		notify_mask &= ~(1 << (domain_id));
		domain_bit = _mali_osk_fls(notify_mask);
	}
}

/*
 * Execute pending power domain changes
 * pm_lock_exec lock must be taken by caller.
 */
static void mali_pm_update_sync_internal(void)
{
	/*
	 * This should only be called in non-atomic context
	 * (normally as deferred work)
	 *
	 * Look at the pending power domain changes, and execute these.
	 * Make sure group and schedulers are notified about changes.
	 */

	struct mali_pmu_core *pmu = mali_pmu_get_global_pmu_core();

	u32 power_down_mask;
	u32 power_up_mask;

	MALI_DEBUG_ASSERT_LOCK_HELD(pm_lock_exec);

#if defined(DEBUG)
	++num_pm_updates;
#endif

	/* Hold PM state lock while we look at (and obey) the wanted state */
	mali_pm_state_lock();

	MALI_DEBUG_PRINT(5, ("PM update pre:  Wanted domain mask: .. [%s]\n",
			     mali_pm_mask_to_string(pd_mask_wanted)));
	MALI_DEBUG_PRINT(5, ("PM update pre:  Current domain mask: . [%s]\n",
			     mali_pm_mask_to_string(pd_mask_current)));
	MALI_DEBUG_PRINT(5, ("PM update pre:  Current PMU mask: .... [%s]\n",
			     mali_pm_mask_to_string(pmu_mask_current)));
	MALI_DEBUG_PRINT(5, ("PM update pre:  Group power stats: ... <%s>\n",
			     mali_pm_group_stats_to_string()));

	/* Figure out which cores we need to power on */
	power_up_mask = pd_mask_wanted &
			(pd_mask_wanted ^ pd_mask_current);

	if (0 != power_up_mask) {
		u32 power_up_mask_pmu;
		struct mali_group *groups_up[MALI_MAX_NUMBER_OF_GROUPS];
		u32 num_groups_up = 0;
		struct mali_l2_cache_core *
			l2_up[MALI_MAX_NUMBER_OF_L2_CACHE_CORES];
		u32 num_l2_up = 0;
		u32 i;

#if defined(DEBUG)
		++num_pm_updates_up;
#endif

		/*
		 * Make sure dummy/global domain is always included when
		 * powering up, since this is controlled by runtime PM,
		 * and device power is on at this stage.
		 */
		power_up_mask |= MALI_PM_DOMAIN_DUMMY_MASK;

		/* Power up only real PMU domains */
		power_up_mask_pmu = power_up_mask & ~MALI_PM_DOMAIN_DUMMY_MASK;

		/* But not those that happen to be powered on already */
		power_up_mask_pmu &= (power_up_mask ^ pmu_mask_current) &
				     power_up_mask;

		if (0 != power_up_mask_pmu) {
			MALI_DEBUG_ASSERT(NULL != pmu);
			pmu_mask_current |= power_up_mask_pmu;
			mali_pmu_power_up(pmu, power_up_mask_pmu);
		}

		/*
		 * Put the domains themselves in power up state.
		 * We get the groups and L2s to notify in return.
		 */
		mali_pm_domain_power_up(power_up_mask,
					groups_up, &num_groups_up,
					l2_up, &num_l2_up);

		/* Need to unlock PM state lock before notifying L2 + groups */
		mali_pm_state_unlock();

		/* Notify each L2 cache that we have be powered up */
		for (i = 0; i < num_l2_up; i++) {
			mali_l2_cache_power_up(l2_up[i]);
		}

		/*
		 * Tell execution module about all the groups we have
		 * powered up. Groups will be notified as a result of this.
		 */
		mali_executor_group_power_up(groups_up, num_groups_up);

		/* Lock state again before checking for power down */
		mali_pm_state_lock();
	}

	/* Figure out which cores we need to power off */
	power_down_mask = pd_mask_current &
			  (pd_mask_wanted ^ pd_mask_current);

	/*
	 * Never power down the dummy/global domain here. This is to be done
	 * from a suspend request (since this domain is only physicall powered
	 * down at that point)
	 */
	power_down_mask &= ~MALI_PM_DOMAIN_DUMMY_MASK;

	if (0 != power_down_mask) {
		u32 power_down_mask_pmu;
		struct mali_group *groups_down[MALI_MAX_NUMBER_OF_GROUPS];
		u32 num_groups_down = 0;
		struct mali_l2_cache_core *
			l2_down[MALI_MAX_NUMBER_OF_L2_CACHE_CORES];
		u32 num_l2_down = 0;
		u32 i;

#if defined(DEBUG)
		++num_pm_updates_down;
#endif

		/*
		 * Put the domains themselves in power down state.
		 * We get the groups and L2s to notify in return.
		 */
		mali_pm_domain_power_down(power_down_mask,
					  groups_down, &num_groups_down,
					  l2_down, &num_l2_down);

		/* Need to unlock PM state lock before notifying L2 + groups */
		mali_pm_state_unlock();

		/*
		 * Tell execution module about all the groups we will be
		 * powering down. Groups will be notified as a result of this.
		 */
		if (0 < num_groups_down) {
			mali_executor_group_power_down(groups_down, num_groups_down);
		}

		/* Notify each L2 cache that we will be powering down */
		for (i = 0; i < num_l2_down; i++) {
			mali_l2_cache_power_down(l2_down[i]);
		}

		/*
		 * Power down only PMU domains which should not stay on
		 * Some domains might for instance currently be incorrectly
		 * powered up if default domain power state is all on.
		 */
		power_down_mask_pmu = pmu_mask_current & (~pd_mask_current);

		if (0 != power_down_mask_pmu) {
			MALI_DEBUG_ASSERT(NULL != pmu);
			pmu_mask_current &= ~power_down_mask_pmu;
			mali_pmu_power_down(pmu, power_down_mask_pmu);

		}
	} else {
		/*
		 * Power down only PMU domains which should not stay on
		 * Some domains might for instance currently be incorrectly
		 * powered up if default domain power state is all on.
		 */
		u32 power_down_mask_pmu;

		/* No need for state lock since we'll only update PMU */
		mali_pm_state_unlock();

		power_down_mask_pmu = pmu_mask_current & (~pd_mask_current);

		if (0 != power_down_mask_pmu) {
			MALI_DEBUG_ASSERT(NULL != pmu);
			pmu_mask_current &= ~power_down_mask_pmu;
			mali_pmu_power_down(pmu, power_down_mask_pmu);
		}
	}

	MALI_DEBUG_PRINT(5, ("PM update post: Current domain mask: . [%s]\n",
			     mali_pm_mask_to_string(pd_mask_current)));
	MALI_DEBUG_PRINT(5, ("PM update post: Current PMU mask: .... [%s]\n",
			     mali_pm_mask_to_string(pmu_mask_current)));
	MALI_DEBUG_PRINT(5, ("PM update post: Group power stats: ... <%s>\n",
			     mali_pm_group_stats_to_string()));
}

static mali_bool mali_pm_common_suspend(void)
{
	mali_pm_state_lock();

	if (0 != pd_mask_wanted) {
		MALI_DEBUG_PRINT(5, ("PM: Aborting suspend operation\n\n\n"));
		mali_pm_state_unlock();
		return MALI_FALSE;
	}

	MALI_DEBUG_PRINT(5, ("PM suspend pre: Wanted domain mask: .. [%s]\n",
			     mali_pm_mask_to_string(pd_mask_wanted)));
	MALI_DEBUG_PRINT(5, ("PM suspend pre: Current domain mask: . [%s]\n",
			     mali_pm_mask_to_string(pd_mask_current)));
	MALI_DEBUG_PRINT(5, ("PM suspend pre: Current PMU mask: .... [%s]\n",
			     mali_pm_mask_to_string(pmu_mask_current)));
	MALI_DEBUG_PRINT(5, ("PM suspend pre: Group power stats: ... <%s>\n",
			     mali_pm_group_stats_to_string()));

	if (0 != pd_mask_current) {
		/*
		 * We have still some domains powered on.
		 * It is for instance very normal that at least the
		 * dummy/global domain is marked as powered on at this point.
		 * (because it is physically powered on until this function
		 * returns)
		 */

		struct mali_group *groups_down[MALI_MAX_NUMBER_OF_GROUPS];
		u32 num_groups_down = 0;
		struct mali_l2_cache_core *
			l2_down[MALI_MAX_NUMBER_OF_L2_CACHE_CORES];
		u32 num_l2_down = 0;
		u32 i;

		/*
		 * Put the domains themselves in power down state.
		 * We get the groups and L2s to notify in return.
		 */
		mali_pm_domain_power_down(pd_mask_current,
					  groups_down,
					  &num_groups_down,
					  l2_down,
					  &num_l2_down);

		MALI_DEBUG_ASSERT(0 == pd_mask_current);
		MALI_DEBUG_ASSERT(MALI_TRUE == mali_pm_domain_all_unused());

		/* Need to unlock PM state lock before notifying L2 + groups */
		mali_pm_state_unlock();

		/*
		 * Tell execution module about all the groups we will be
		 * powering down. Groups will be notified as a result of this.
		 */
		if (0 < num_groups_down) {
			mali_executor_group_power_down(groups_down, num_groups_down);
		}

		/* Notify each L2 cache that we will be powering down */
		for (i = 0; i < num_l2_down; i++) {
			mali_l2_cache_power_down(l2_down[i]);
		}

		pmu_mask_current = 0;
	} else {
		MALI_DEBUG_ASSERT(0 == pmu_mask_current);

		MALI_DEBUG_ASSERT(MALI_TRUE == mali_pm_domain_all_unused());

		mali_pm_state_unlock();
	}

	MALI_DEBUG_PRINT(5, ("PM suspend post: Current domain mask:  [%s]\n",
			     mali_pm_mask_to_string(pd_mask_current)));
	MALI_DEBUG_PRINT(5, ("PM suspend post: Current PMU mask: ... [%s]\n",
			     mali_pm_mask_to_string(pmu_mask_current)));
	MALI_DEBUG_PRINT(5, ("PM suspend post: Group power stats: .. <%s>\n",
			     mali_pm_group_stats_to_string()));

	return MALI_TRUE;
}

static void mali_pm_update_work(void *data)
{
	MALI_IGNORE(data);
	mali_pm_update_sync();
}

static _mali_osk_errcode_t mali_pm_create_pm_domains(void)
{
	int i;

	/* Create all domains (including dummy domain) */
	for (i = 0; i < MALI_MAX_NUMBER_OF_DOMAINS; i++) {
		if (0x0 == domain_config[i]) continue;

		if (NULL == mali_pm_domain_create(domain_config[i])) {
			return _MALI_OSK_ERR_NOMEM;
		}
	}

	return _MALI_OSK_ERR_OK;
}

static void mali_pm_set_default_pm_domain_config(void)
{
	MALI_DEBUG_ASSERT(0 != _mali_osk_resource_base_address());

	/* GP core */
	if (_MALI_OSK_ERR_OK == _mali_osk_resource_find(
		    MALI_OFFSET_GP, NULL)) {
		domain_config[MALI_DOMAIN_INDEX_GP] = 0x01;
	}

	/* PP0 - PP3 core */
	if (_MALI_OSK_ERR_OK == _mali_osk_resource_find(
		    MALI_OFFSET_PP0, NULL)) {
		if (mali_is_mali400()) {
			domain_config[MALI_DOMAIN_INDEX_PP0] = 0x01 << 2;
		} else if (mali_is_mali450()) {
			domain_config[MALI_DOMAIN_INDEX_PP0] = 0x01 << 1;
		} else if (mali_is_mali470()) {
			domain_config[MALI_DOMAIN_INDEX_PP0] = 0x01 << 0;
		}
	}

	if (_MALI_OSK_ERR_OK == _mali_osk_resource_find(
		    MALI_OFFSET_PP1, NULL)) {
		if (mali_is_mali400()) {
			domain_config[MALI_DOMAIN_INDEX_PP1] = 0x01 << 3;
		} else if (mali_is_mali450()) {
			domain_config[MALI_DOMAIN_INDEX_PP1] = 0x01 << 2;
		} else if (mali_is_mali470()) {
			domain_config[MALI_DOMAIN_INDEX_PP1] = 0x01 << 1;
		}
	}

	if (_MALI_OSK_ERR_OK == _mali_osk_resource_find(
		    MALI_OFFSET_PP2, NULL)) {
		if (mali_is_mali400()) {
			domain_config[MALI_DOMAIN_INDEX_PP2] = 0x01 << 4;
		} else if (mali_is_mali450()) {
			domain_config[MALI_DOMAIN_INDEX_PP2] = 0x01 << 2;
		} else if (mali_is_mali470()) {
			domain_config[MALI_DOMAIN_INDEX_PP2] = 0x01 << 1;
		}
	}

	if (_MALI_OSK_ERR_OK == _mali_osk_resource_find(
		    MALI_OFFSET_PP3, NULL)) {
		if (mali_is_mali400()) {
			domain_config[MALI_DOMAIN_INDEX_PP3] = 0x01 << 5;
		} else if (mali_is_mali450()) {
			domain_config[MALI_DOMAIN_INDEX_PP3] = 0x01 << 2;
		} else if (mali_is_mali470()) {
			domain_config[MALI_DOMAIN_INDEX_PP3] = 0x01 << 1;
		}
	}

	/* PP4 - PP7 */
	if (_MALI_OSK_ERR_OK == _mali_osk_resource_find(
		    MALI_OFFSET_PP4, NULL)) {
		domain_config[MALI_DOMAIN_INDEX_PP4] = 0x01 << 3;
	}

	if (_MALI_OSK_ERR_OK == _mali_osk_resource_find(
		    MALI_OFFSET_PP5, NULL)) {
		domain_config[MALI_DOMAIN_INDEX_PP5] = 0x01 << 3;
	}

	if (_MALI_OSK_ERR_OK == _mali_osk_resource_find(
		    MALI_OFFSET_PP6, NULL)) {
		domain_config[MALI_DOMAIN_INDEX_PP6] = 0x01 << 3;
	}

	if (_MALI_OSK_ERR_OK == _mali_osk_resource_find(
		    MALI_OFFSET_PP7, NULL)) {
		domain_config[MALI_DOMAIN_INDEX_PP7] = 0x01 << 3;
	}

	/* L2gp/L2PP0/L2PP4 */
	if (mali_is_mali400()) {
		if (_MALI_OSK_ERR_OK == _mali_osk_resource_find(
			    MALI400_OFFSET_L2_CACHE0, NULL)) {
			domain_config[MALI_DOMAIN_INDEX_L20] = 0x01 << 1;
		}
	} else if (mali_is_mali450()) {
		if (_MALI_OSK_ERR_OK == _mali_osk_resource_find(
			    MALI450_OFFSET_L2_CACHE0, NULL)) {
			domain_config[MALI_DOMAIN_INDEX_L20] = 0x01 << 0;
		}

		if (_MALI_OSK_ERR_OK == _mali_osk_resource_find(
			    MALI450_OFFSET_L2_CACHE1, NULL)) {
			domain_config[MALI_DOMAIN_INDEX_L21] = 0x01 << 1;
		}

		if (_MALI_OSK_ERR_OK == _mali_osk_resource_find(
			    MALI450_OFFSET_L2_CACHE2, NULL)) {
			domain_config[MALI_DOMAIN_INDEX_L22] = 0x01 << 3;
		}
	} else if (mali_is_mali470()) {
		if (_MALI_OSK_ERR_OK == _mali_osk_resource_find(
			    MALI470_OFFSET_L2_CACHE1, NULL)) {
			domain_config[MALI_DOMAIN_INDEX_L21] = 0x01 << 0;
		}
	}
}

static u32 mali_pm_get_registered_cores_mask(void)
{
	int i = 0;
	u32 mask = 0;

	for (i = 0; i < MALI_DOMAIN_INDEX_DUMMY; i++) {
		mask |= domain_config[i];
	}

	return mask;
}

static void mali_pm_set_pmu_domain_config(void)
{
	int i = 0;

	_mali_osk_device_data_pmu_config_get(domain_config, MALI_MAX_NUMBER_OF_DOMAINS - 1);

	for (i = 0; i < MALI_MAX_NUMBER_OF_DOMAINS - 1; i++) {
		if (0 != domain_config[i]) {
			MALI_DEBUG_PRINT(2, ("Using customer pmu config:\n"));
			break;
		}
	}

	if (MALI_MAX_NUMBER_OF_DOMAINS - 1 == i) {
		MALI_DEBUG_PRINT(2, ("Using hw detect pmu config:\n"));
		mali_pm_set_default_pm_domain_config();
	}

	for (i = 0; i < MALI_MAX_NUMBER_OF_DOMAINS - 1; i++) {
		if (domain_config[i]) {
			MALI_DEBUG_PRINT(2, ("domain_config[%d] = 0x%x \n", i, domain_config[i]));
		}
	}
	/* Can't override dummy domain mask */
	domain_config[MALI_DOMAIN_INDEX_DUMMY] =
		1 << MALI_DOMAIN_INDEX_DUMMY;
}

#if defined(DEBUG)
const char *mali_pm_mask_to_string(u32 mask)
{
	static char bit_str[MALI_MAX_NUMBER_OF_DOMAINS + 1];
	int bit;
	int str_pos = 0;

	/* Must be protected by lock since we use shared string buffer */
	if (NULL != pm_lock_exec) {
		MALI_DEBUG_ASSERT_LOCK_HELD(pm_lock_exec);
	}

	for (bit = MALI_MAX_NUMBER_OF_DOMAINS - 1; bit >= 0; bit--) {
		if (mask & (1 << bit)) {
			bit_str[str_pos] = 'X';
		} else {
			bit_str[str_pos] = '-';
		}
		str_pos++;
	}

	bit_str[MALI_MAX_NUMBER_OF_DOMAINS] = '\0';

	return bit_str;
}

const char *mali_pm_group_stats_to_string(void)
{
	static char bit_str[MALI_MAX_NUMBER_OF_GROUPS + 1];
	u32 num_groups = mali_group_get_glob_num_groups();
	u32 i;

	/* Must be protected by lock since we use shared string buffer */
	if (NULL != pm_lock_exec) {
		MALI_DEBUG_ASSERT_LOCK_HELD(pm_lock_exec);
	}

	for (i = 0; i < num_groups && i < MALI_MAX_NUMBER_OF_GROUPS; i++) {
		struct mali_group *group;

		group = mali_group_get_glob_group(i);

		if (MALI_TRUE == mali_group_power_is_on(group)) {
			bit_str[i] = 'X';
		} else {
			bit_str[i] = '-';
		}
	}

	bit_str[i] = '\0';

	return bit_str;
}
#endif

/*
 * num_pp is the number of PP cores which will be powered on given this mask
 * cost is the total power cost of cores which will be powered on given this mask
 */
static void mali_pm_stat_from_mask(u32 mask, u32 *num_pp, u32 *cost)
{
	u32 i;

	/* loop through all cores */
	for (i = 0; i < MALI_MAX_NUMBER_OF_DOMAINS; i++) {
		if (!(domain_config[i] & mask)) {
			continue;
		}

		switch (i) {
		case MALI_DOMAIN_INDEX_GP:
			*cost += MALI_GP_COST;

			break;
		case MALI_DOMAIN_INDEX_PP0: /* Fall through */
		case MALI_DOMAIN_INDEX_PP1: /* Fall through */
		case MALI_DOMAIN_INDEX_PP2: /* Fall through */
		case MALI_DOMAIN_INDEX_PP3:
			if (mali_is_mali400()) {
				if ((domain_config[MALI_DOMAIN_INDEX_L20] & mask)
				    || (domain_config[MALI_DOMAIN_INDEX_DUMMY]
					== domain_config[MALI_DOMAIN_INDEX_L20])) {
					*num_pp += 1;
				}
			} else {
				if ((domain_config[MALI_DOMAIN_INDEX_L21] & mask)
				    || (domain_config[MALI_DOMAIN_INDEX_DUMMY]
					== domain_config[MALI_DOMAIN_INDEX_L21])) {
					*num_pp += 1;
				}
			}

			*cost += MALI_PP_COST;
			break;
		case MALI_DOMAIN_INDEX_PP4: /* Fall through */
		case MALI_DOMAIN_INDEX_PP5: /* Fall through */
		case MALI_DOMAIN_INDEX_PP6: /* Fall through */
		case MALI_DOMAIN_INDEX_PP7:
			MALI_DEBUG_ASSERT(mali_is_mali450());

			if ((domain_config[MALI_DOMAIN_INDEX_L22] & mask)
			    || (domain_config[MALI_DOMAIN_INDEX_DUMMY]
				== domain_config[MALI_DOMAIN_INDEX_L22])) {
				*num_pp += 1;
			}

			*cost += MALI_PP_COST;
			break;
		case MALI_DOMAIN_INDEX_L20: /* Fall through */
		case MALI_DOMAIN_INDEX_L21: /* Fall through */
		case MALI_DOMAIN_INDEX_L22:
			*cost += MALI_L2_COST;

			break;
		}
	}
}

void mali_pm_power_cost_setup(void)
{
	/*
	 * Two parallel arrays which store the best domain mask and its cost
	 * The index is the number of PP cores, E.g. Index 0 is for 1 PP option,
	 * might have mask 0x2 and with cost of 1, lower cost is better
	 */
	u32 best_mask[MALI_MAX_NUMBER_OF_PHYSICAL_PP_GROUPS] = { 0 };
	u32 best_cost[MALI_MAX_NUMBER_OF_PHYSICAL_PP_GROUPS] = { 0 };
	/* Array cores_in_domain is used to store the total pp cores in each pm domain. */
	u32 cores_in_domain[MALI_MAX_NUMBER_OF_DOMAINS] = { 0 };
	/* Domain_count is used to represent the max domain we have.*/
	u32 max_domain_mask = 0;
	u32 max_domain_id = 0;
	u32 always_on_pp_cores = 0;

	u32 num_pp, cost, mask;
	u32 i, j , k;

	/* Initialize statistics */
	for (i = 0; i < MALI_MAX_NUMBER_OF_PHYSICAL_PP_GROUPS; i++) {
		best_mask[i] = 0;
		best_cost[i] = 0xFFFFFFFF; /* lower cost is better */
	}

	for (i = 0; i < MALI_MAX_NUMBER_OF_PHYSICAL_PP_GROUPS + 1; i++) {
		for (j = 0; j < MALI_MAX_NUMBER_OF_DOMAINS; j++) {
			mali_pm_domain_power_cost_result[i][j] = 0;
		}
	}

	/* Caculate number of pp cores of a given domain config. */
	for (i = MALI_DOMAIN_INDEX_PP0; i <= MALI_DOMAIN_INDEX_PP7; i++) {
		if (0 < domain_config[i]) {
			/* Get the max domain mask value used to caculate power cost
			 * and we don't count in always on pp cores. */
			if (MALI_PM_DOMAIN_DUMMY_MASK != domain_config[i]
			    && max_domain_mask < domain_config[i]) {
				max_domain_mask = domain_config[i];
			}

			if (MALI_PM_DOMAIN_DUMMY_MASK == domain_config[i]) {
				always_on_pp_cores++;
			}
		}
	}
	max_domain_id = _mali_osk_fls(max_domain_mask);

	/*
	 * Try all combinations of power domains and check how many PP cores
	 * they have and their power cost.
	 */
	for (mask = 0; mask < (1 << max_domain_id); mask++) {
		num_pp = 0;
		cost = 0;

		mali_pm_stat_from_mask(mask, &num_pp, &cost);

		/* This mask is usable for all MP1 up to num_pp PP cores, check statistics for all */
		for (i = 0; i < num_pp; i++) {
			if (best_cost[i] >= cost) {
				best_cost[i] = cost;
				best_mask[i] = mask;
			}
		}
	}

	/*
	 * If we want to enable x pp cores, if x is less than number of always_on pp cores,
	 * all of pp cores we will enable must be always_on pp cores.
	 */
	for (i = 0; i < mali_executor_get_num_cores_total(); i++) {
		if (i < always_on_pp_cores) {
			mali_pm_domain_power_cost_result[i + 1][MALI_MAX_NUMBER_OF_DOMAINS - 1]
				= i + 1;
		} else {
			mali_pm_domain_power_cost_result[i + 1][MALI_MAX_NUMBER_OF_DOMAINS - 1]
				= always_on_pp_cores;
		}
	}

	/* In this loop, variable i represent for the number of non-always on pp cores we want to enabled. */
	for (i = 0; i < (mali_executor_get_num_cores_total() - always_on_pp_cores); i++) {
		if (best_mask[i] == 0) {
			/* This MP variant is not available */
			continue;
		}

		for (j = 0; j < MALI_MAX_NUMBER_OF_DOMAINS; j++) {
			cores_in_domain[j] = 0;
		}

		for (j = MALI_DOMAIN_INDEX_PP0; j <= MALI_DOMAIN_INDEX_PP7; j++) {
			if (0 < domain_config[j]
			    && (MALI_PM_DOMAIN_DUMMY_MASK != domain_config[i])) {
				cores_in_domain[_mali_osk_fls(domain_config[j]) - 1]++;
			}
		}

		/* In this loop, j represent for the number we have already enabled.*/
		for (j = 0; j <= i;) {
			/* j used to visit all of domain to get the number of pp cores remained in it. */
			for (k = 0; k < max_domain_id; k++) {
				/* If domain k in best_mask[i] is enabled and this domain has extra pp cores,
				 * we know we must pick at least one pp core from this domain.
				 * And then we move to next enabled pm domain. */
				if ((best_mask[i] & (0x1 << k)) && (0 < cores_in_domain[k])) {
					cores_in_domain[k]--;
					mali_pm_domain_power_cost_result[always_on_pp_cores + i + 1][k]++;
					j++;
					if (j > i) {
						break;
					}
				}
			}
		}
	}
}

/*
 * When we are doing core scaling,
 * this function is called to return the best mask to
 * achieve the best pp group power cost.
 */
void mali_pm_get_best_power_cost_mask(int num_requested, int *dst)
{
	MALI_DEBUG_ASSERT((mali_executor_get_num_cores_total() >= num_requested) && (0 <= num_requested));

	_mali_osk_memcpy(dst, mali_pm_domain_power_cost_result[num_requested], MALI_MAX_NUMBER_OF_DOMAINS * sizeof(int));
}

u32 mali_pm_get_current_mask(void)
{
	return pd_mask_current;
}

u32 mali_pm_get_wanted_mask(void)
{
	return pd_mask_wanted;
}
